package com.novoda.runbuddy.activity.stopwatch;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;

public class StopwatchTools {

	private static final String	SIMPLE_FORMATTER_STYLE_FOR_DISPLAY	= "HH:mm:ss.SS";
	private static final int	SECONDS_IN_MINUTE					= 60;
	private static final int	CENTISECONDS_IN_MINUTE				= 1060;
	private static final int	SECONDS_IN_HOUR						= 3600;
	private static final int	HOURS_IN_A_DAY						= 24;
	private static final int	MILLISEC_IN_SEC						= 1000;
	private static final int	MILLISEC_IN_MIN						= 60 * MILLISEC_IN_SEC;
	private static final int	MILLISEC_IN_HOUR					= 60 * MILLISEC_IN_MIN;
	private static final int	DAYS_IN_A_YEAR						= 365;
	public static final int		HOUR_THRESHHOLD						= 24;
	public static final int		MINUTE_THRESHHOLD					= 59;
	public static final int		SECOND_THRESHHOLD					= 59;
	public static final int		CENTISEC_THRESHHOLD					= 99;
	public static final int		LOWEST_POSSIBLE						= 00;
	private static final int	TWO_DIGITS_TEN						= 10;
	public static final Locale	locale								= new Locale("UK");

	public static final int		EQUAL								= 0;
	public static final int		GREATER_THAN						= 1;
	public static final int		LESS_THAN							= -1;

	public static final int		CENTISECS							= 0;
	public static final int		SECS								= 1;
	public static final int		MINS								= 2;
	public static final int		HOURS								= 3;

	// This needs refactoring
	public static String updateFormattedString(String ammendmentOperator, String currentTime, Integer incrementType, int byAmount) {

		String numericValue = null;

		switch (incrementType) {
			case StopwatchTools.CENTISECS:

				if (getCentiSecs(currentTime) == CENTISEC_THRESHHOLD && ammendmentOperator.equals("++")) {

					if (getSecs(currentTime) == SECOND_THRESHHOLD) {

						if (getMins(currentTime) == MINUTE_THRESHHOLD) {
							currentTime = setHours(Integer.parseInt(incrementHours(ammendmentOperator, currentTime, 1)),
									currentTime);
							currentTime = setMins(0, currentTime);
							currentTime = setSecs(0, currentTime);
							currentTime = setCentiSecs(0, currentTime);
						} else {
							// increment secs
							currentTime = setMins(Integer.parseInt(incrementMins(ammendmentOperator, currentTime, 1)), currentTime);
							currentTime = setSecs(0, currentTime);
							currentTime = setCentiSecs(0, currentTime);
						}

					} else {
						currentTime = setSecs(Integer.parseInt(incrementSeconds(ammendmentOperator, currentTime, 1)), currentTime);
						currentTime = setCentiSecs(0, currentTime);
					}

				} else {
					numericValue = incrementCentiSeconds(ammendmentOperator, currentTime, 1);
					currentTime = setCentiSecs(Integer.parseInt(numericValue), currentTime);
				}
				break;

			case StopwatchTools.SECS:
				if (getSecs(currentTime) == SECOND_THRESHHOLD && ammendmentOperator.equals("++")) {
					currentTime = setMins(Integer.parseInt(incrementMins(ammendmentOperator, currentTime, 1)), currentTime);
					currentTime = setSecs(0, currentTime);
				} else {
					numericValue = incrementSeconds(ammendmentOperator, currentTime, 1);
					currentTime = setSecs(Integer.parseInt(numericValue), currentTime);
				}
				break;
			case StopwatchTools.MINS:
				if (getMins(currentTime) == MINUTE_THRESHHOLD && ammendmentOperator.equals("++")) {
					numericValue = setHours(Integer.parseInt(incrementHours(ammendmentOperator, currentTime, 1)), currentTime);
					currentTime = setMins(0, currentTime);
				} else {
					numericValue = incrementMins(ammendmentOperator, currentTime, 1);
					currentTime = setMins(Integer.parseInt(numericValue), currentTime);
				}
				break;
			case StopwatchTools.HOURS:
				numericValue = incrementHours(ammendmentOperator, currentTime, 1);
				currentTime = setHours(Integer.parseInt(numericValue), currentTime);
				break;
		}

		return currentTime;

	}

	private static String incrementCentiSeconds(String ammendmentOperator, String currentTime, int byAmount) {
		Integer digits = getCentiSecs(currentTime);
		return incrementWithoutRolling(ammendmentOperator, byAmount, digits, CENTISEC_THRESHHOLD, LOWEST_POSSIBLE);
	}

	protected static String incrementSeconds(String ammendmentOperator, String currentTime, int byAmount) {
		Integer digits = getSecs(currentTime);
		return incrementWithoutRolling(ammendmentOperator, byAmount, digits, SECOND_THRESHHOLD, LOWEST_POSSIBLE);
	}

	private static String incrementMins(String ammendmentOperator, String currentTime, int byAmount) {
		Integer digits = getMins(currentTime);
		return incrementWithoutRolling(ammendmentOperator, byAmount, digits, MINUTE_THRESHHOLD, LOWEST_POSSIBLE);
	}

	private static String incrementHours(String ammendmentOperator, String currentTime, int byAmount) {
		Integer digits = getHours(currentTime);
		return incrementWithoutRolling(ammendmentOperator, byAmount, digits, HOUR_THRESHHOLD, LOWEST_POSSIBLE);
	}

	private static String incrementWithoutRolling(String operator, int byAmount, Integer digitsInTime, int highestThreshold,
			int lowestThreshold) {
		for (int i = 0; i < byAmount; i++) {
			digitsInTime = new Integer(digitsAmmendedByOperator(digitsInTime, operator, highestThreshold, lowestThreshold));
		}

		return stringOfIntInExpectedFormat(digitsInTime);
	}

	public static String updateSecs(String operator, String currentTime) {

		String returnedTime = null;

		String hours = getHoursString(currentTime);
		String mins = getMinsString(currentTime);
		Integer secs = getSecs(currentTime);
		String centiSecs = getCentiSecsString(currentTime);

		String returnedDigits = digitsAmmendedByOperator(secs, operator, SECOND_THRESHHOLD, LOWEST_POSSIBLE);

		if (returnedDigits.equals(SECOND_THRESHHOLD)) {
			returnedTime = "00:00:00.00";
		} else {
			returnedTime = new String(hours + ":" + mins + ":" + returnedDigits + "." + centiSecs);
		}

		return returnedTime;

	}

	public static String updateCentiSecs(String operator, String currentTime) {

		String returnedTime = null;

		String hours = getHoursString(currentTime);
		String mins = getMinsString(currentTime);
		String secs = getSecsString(currentTime);
		Integer centiSecs = getCentiSecs(currentTime);

		String returnedDigits = digitsAmmendedByOperator(centiSecs, operator, CENTISEC_THRESHHOLD, LOWEST_POSSIBLE);

		if (returnedDigits.equals(SECOND_THRESHHOLD)) {
			returnedTime = "00:00:00.00";
		} else {
			returnedTime = new String(hours + ":" + mins + ":" + secs + "." + returnedDigits);
		}

		return returnedTime;

	}

	public static String updateMins(String operator, String currentTime) {

		String returnedTime = null;
		String mins = null;
		String hours = null;

		Integer newTime = getMins(currentTime);

		if (getHours(currentTime) < 10) {
			hours = "0" + getHours(currentTime.toString());
		} else {
			hours = getHours(currentTime).toString();
		}

		String end = getAfterMins(currentTime);
		mins = digitsAmmendedByOperator(newTime, operator, MINUTE_THRESHHOLD, LOWEST_POSSIBLE);

		if (mins.equals(MINUTE_THRESHHOLD)) {
			returnedTime = "00:00:00.00";
		} else {
			returnedTime = new String(hours + ":" + mins + end);
		}

		return returnedTime;

	}

	public static String updateHours(String operator, String currentTime) {

		String returnedTime = null;
		String hours = null;

		Integer newTime = getHours(currentTime);
		String other = getEverythingButHours(currentTime);
		hours = digitsAmmendedByOperator(newTime, operator, HOUR_THRESHHOLD, LOWEST_POSSIBLE);

		if (hours.equals("0")) {
			hours = hours.concat("0");
		}

		returnedTime = new String(hours + other);

		return returnedTime;

	}

	public static String getAfterMins(String currentTime) {
		return currentTime.substring(5);
	}

	public static String digitsAmmendedByOperator(Integer digitsInTime, String operator, int highest_poss, int lowest_poss) {
		String centiSecs = null;

		if (operator.equals("++")) {
			if (digitsInTime == highest_poss) {
				centiSecs = stringOfIntInExpectedFormat(new Integer(lowest_poss));
			} else {
				digitsInTime++;
				digitsInTime = setNoWithRoller(digitsInTime, highest_poss);
				centiSecs = stringOfIntInExpectedFormat(digitsInTime);
			}
		}

		if (operator.equals("--")) {
			if (digitsInTime == lowest_poss) {
				centiSecs = stringOfIntInExpectedFormat(new Integer(lowest_poss));
			} else {
				digitsInTime--;
				centiSecs = stringOfIntInExpectedFormat(digitsInTime);
			}
		}

		return centiSecs;
	}

	public static String getEverythingButHours(String currentTime) {
		return currentTime.substring(2);
	}

	public static Integer setNoWithRoller(Integer newTime, int roller) {

		if ((newTime + newTime) < roller) {
			return newTime++;
		} else {

			while (newTime > roller) {
				newTime = (newTime - roller);
			}

			if (newTime > roller) {
				return new Integer(0);
			}

		}

		return newTime;
	}

	public static String stringOfIntInExpectedFormat(Integer newTime) {
		String secs;

		if (newTime < TWO_DIGITS_TEN) {
			secs = (newTime).toString();
			secs = "0" + secs;
		} else {
			secs = (newTime).toString();
		}

		return secs;
	}

	public static Integer getCentiSecs(String formattedTime) {
		return new Integer(stringOfIntInExpectedFormat(Integer.parseInt(formattedTime.substring(9, 11))));
	}

	public static String getCentiSecsString(String formattedTime) {
		return stringOfIntInExpectedFormat(Integer.parseInt(formattedTime.substring(9, 11)));
	}

	public static Integer getSecs(String formattedString) {
		return new Integer(stringOfIntInExpectedFormat(Integer.parseInt(formattedString.substring(6, 8))));
	}

	public static String getSecsString(String formattedString) {
		return stringOfIntInExpectedFormat(Integer.parseInt(formattedString.substring(6, 8)));
	}

	public static String getMinsString(String formattedString) {
		return stringOfIntInExpectedFormat(Integer.parseInt(formattedString.substring(3, 5)));
	}

	public static Integer getMins(String formattedString) {
		return new Integer(stringOfIntInExpectedFormat(Integer.parseInt(formattedString.substring(3, 5))));
	}

	public static String getHoursString(String formattedString) {
		return stringOfIntInExpectedFormat(Integer.parseInt(formattedString.substring(0, 2)));
	}

	public static Integer getHours(String formattedString) {
		return new Integer(stringOfIntInExpectedFormat(Integer.parseInt(formattedString.substring(0, 2))));
	}

	public static String setHours(Integer hours, String formattedString) {

		StringBuffer stringBuffer = new StringBuffer(formattedString);

		if (hours < 00 || hours > HOUR_THRESHHOLD) {
			stringBuffer.replace(0, 3, hours.toString());
			stringBuffer.insert(0, "00");
			return stringBuffer.toString();
		} else if (hours < 10) {
			stringBuffer.replace(0, 2, hours.toString());
			stringBuffer.insert(0, "0");
			return stringBuffer.toString();
		}

		stringBuffer.replace(0, 2, hours.toString());
		return stringBuffer.toString();
	}

	public static String setSecs(Integer secs, String formattedString) {
		StringBuffer stringBuffer = new StringBuffer(formattedString);

		if (secs < 00 || secs > SECOND_THRESHHOLD) {
			stringBuffer.replace(6, 9, secs.toString());
			stringBuffer.insert(6, "00");
			return stringBuffer.toString();
		} else if (secs < 10) {
			stringBuffer.replace(6, 8, secs.toString());
			stringBuffer.insert(6, "0");
			return stringBuffer.toString();
		}

		stringBuffer.replace(6, 8, secs.toString());
		return stringBuffer.toString();
	}

	public static String setMins(Integer mins, String formattedString) {
		StringBuffer stringBuffer = new StringBuffer(formattedString);

		if (mins < 00 || mins > MINUTE_THRESHHOLD) {
			stringBuffer.replace(3, 6, mins.toString());
			stringBuffer.insert(3, "00");
			return stringBuffer.toString();
		} else if (mins < 10) {
			stringBuffer.replace(3, 5, mins.toString());
			stringBuffer.insert(3, "0");
			return stringBuffer.toString();
		}

		stringBuffer.replace(3, 5, mins.toString());
		return stringBuffer.toString();
	}

	public static String setCentiSecs(Integer centiSecs, String formattedString) {

		StringBuffer stringBuffer = new StringBuffer(formattedString);

		if (centiSecs < 00 || centiSecs > CENTISEC_THRESHHOLD) {
			stringBuffer.replace(9, 12, centiSecs.toString());
			stringBuffer.insert(9, "00");
			return stringBuffer.toString();
		} else if (centiSecs < 10) {
			stringBuffer.replace(9, 11, centiSecs.toString());
			stringBuffer.insert(9, "0");
			return stringBuffer.toString();
		}

		stringBuffer.replace(9, 11, centiSecs.toString());
		return stringBuffer.toString();
	}

	public static int compare(String timeYouCareAbout, String comparison) {

		long target = getMilliSecs(timeYouCareAbout);
		long againstThis = getMilliSecs(comparison);

		if (againstThis == target) {
			return EQUAL;
		}

		if (againstThis > target) {
			return GREATER_THAN;
		}

		if (againstThis < target) {
			return LESS_THAN;
		}

		return 0;
	}

	public static String getTimeInExpectedFormat(long time) {
		GregorianCalendar calendar = new GregorianCalendar();
		int dstOffSet = getIfDSTCalendarTimeOffset(calendar);
		calendar.setTimeInMillis(time);

		if (dstOffSet > 0) {
			calendar.set(Calendar.HOUR, calendar.get(Calendar.HOUR) - dstOffSet);
		}

		SimpleDateFormat dateFormat = new SimpleDateFormat(SIMPLE_FORMATTER_STYLE_FOR_DISPLAY);
		String formattedDate = dateFormat.format(calendar.getTime());
		return formattedDate;
	}
	
	public static synchronized String getStringInExpectedFormat(Long elapsedTime) {
		long hours;
		long minutes;
		long seconds;
		String hourString;
		String minuteString;
		String secondsString;
		String msString;
		hours = elapsedTime / (1000 * 60 * 60);
		hourString = Long.toString(hours);
		while (hourString.length() < 2) {
			hourString = "0" + hourString;
		}
		
		elapsedTime = elapsedTime - (hours * 1000 * 60 * 60);

		minutes = elapsedTime / (1000 * 60);
		minuteString = Long.toString(minutes);
		while (minuteString.length() < 2) {
			minuteString = "0" + minuteString;
		}
		
		elapsedTime = elapsedTime - (minutes * 1000 * 60);

		seconds = elapsedTime / 1000;
		secondsString = Long.toString(seconds);
		while (secondsString.length() < 2) {
			secondsString = "0" + secondsString;
		}
		
		elapsedTime = elapsedTime - (seconds * 1000);

		msString = Long.toString(elapsedTime / 10);
		while (msString.length() < 2)
			msString = "0" + msString;

		return hourString + ":" + minuteString + ":" + secondsString + "." + msString;
	}

	private static int getIfDSTCalendarTimeOffset(GregorianCalendar calendar) {
		return calendar.get(Calendar.DST_OFFSET) / (60 * 60 * 1000);
	}

	public static long getMilliSecs(String formattedString) {
		Calendar calendar = new GregorianCalendar();
		calendar.set(Calendar.YEAR, 0);
		calendar.set(Calendar.MONTH, 0);
		calendar.set(Calendar.DAY_OF_MONTH, 1);

		calendar.set(Calendar.HOUR, getHours(formattedString));
		calendar.set(Calendar.MINUTE, getMins(formattedString));
		calendar.set(Calendar.SECOND, getSecs(formattedString));
		calendar.set(Calendar.MILLISECOND, getCentiSecs(formattedString));

		return calendar.getTimeInMillis();
	}

	public static boolean inTheSameDay(long time1, long time2) {
		GregorianCalendar cal = new GregorianCalendar();
		cal.setTimeInMillis(time1);

		GregorianCalendar cal2 = new GregorianCalendar();
		cal2.setTimeInMillis(time2);

		if (cal.get(java.util.Calendar.YEAR) == cal2.get(java.util.Calendar.YEAR)
				&& cal.get(java.util.Calendar.DAY_OF_YEAR) == cal2.get(java.util.Calendar.DAY_OF_YEAR)) {
			return true;
		}

		return false;
	}

	public static long getDiffMilli(long before, long after) {
		return after - before;
	}

	public static Long getDiffSecs(long before, long after) {
		return new Long(getDiffMilli(before, after) / MILLISEC_IN_SEC);
	}

	public static Long getDiffMins(long before, long after) {
		return new Long(getDiffMilli(before, after) / MILLISEC_IN_MIN);
	}

	public static Long getDiffHours(long before, long after) {
		return new Long(getDiffMilli(before, after) / MILLISEC_IN_HOUR);
	}

	public static Long getDiffDays(long before, long after) {
		return new Long(getDiffHours(before, after) / HOURS_IN_A_DAY);
	}

	public static Long getDiffYears(long before, long after) {
		return new Long(getDiffDays(before, after) / DAYS_IN_A_YEAR);
	}

	public static String getFormattedDiff(long before, long after) {

		GregorianCalendar beforeCal = new GregorianCalendar();
		GregorianCalendar afterCal = new GregorianCalendar();

		beforeCal.setTimeInMillis(before);
		afterCal.setTimeInMillis(after);

		long elapsed = getElapsedSeconds(beforeCal, afterCal);

		return formattedCalcHMS(elapsed);
	}

	public static long[] getDiff(long before, long after) {

		GregorianCalendar beforeCal = new GregorianCalendar();
		GregorianCalendar afterCal = new GregorianCalendar();

		beforeCal.setTimeInMillis(before);
		afterCal.setTimeInMillis(after);

		long elapsed = getElapsedSeconds(beforeCal, afterCal);

		return calcHMS(elapsed);
	}

	// Not accurate enough.
	public static long getElapsedSeconds(GregorianCalendar gc1, GregorianCalendar gc2) {
		Date d1 = gc1.getTime();
		Date d2 = gc2.getTime();
		long l1 = d1.getTime();
		long l2 = d2.getTime();
		long difference = Math.abs(l2 - l1);
		return difference / 1000;
	}

	// ToDo: This function is not generating the expected result. Somehow there
	// is rounding taking place that involves the parts of the seonds that I
	// have not printed.
	// Even if I could just print them that would be fine.
	public static String formattedCalcHMS(long timeInSeconds) {
		long hours, minutes, seconds, secondsLeftOver;

		hours = timeInSeconds / SECONDS_IN_HOUR;
		secondsLeftOver = timeInSeconds - (hours * SECONDS_IN_HOUR);

		minutes = secondsLeftOver / SECONDS_IN_MINUTE;
		seconds = secondsLeftOver - (minutes * SECONDS_IN_MINUTE);

		return hours + " Hours " + minutes + " mins " + seconds + " secs";
	}

	public static long[] calcHMS(long timeInSeconds) {

		int hoursArray = 3, minutesArray = 2, secondsArray = 1, centiArray = 0;
		long hours = 0, minutes = 0, seconds = 0, centi = 0;
		long secondsLeftOver;
		long[] hoursMinSecsCenti = { 0, 0, 0, 0 };

		hours = timeInSeconds / SECONDS_IN_HOUR;
		secondsLeftOver = timeInSeconds - (hours * SECONDS_IN_HOUR);

		minutes = secondsLeftOver / SECONDS_IN_MINUTE;
		seconds = secondsLeftOver - (minutes * SECONDS_IN_MINUTE);
		centi = secondsLeftOver - (minutes * CENTISECONDS_IN_MINUTE);

		hoursMinSecsCenti[centiArray] = centi;
		hoursMinSecsCenti[secondsArray] = seconds;
		hoursMinSecsCenti[minutesArray] = minutes;
		hoursMinSecsCenti[hoursArray] = hours;

		return hoursMinSecsCenti;
	}

	// public static String[] calcHMSAsStrings(long timeInSeconds) {
	// long hours, minutes, seconds, secondsLeftOver;
	// String[] hoursMinSecsCenti = {"","","",""};
	//
	// hours = timeInSeconds / SECONDS_IN_HOUR;
	// secondsLeftOver = timeInSeconds - (hours * SECONDS_IN_HOUR);
	//
	// minutes = secondsLeftOver / SECONDS_IN_MINUTE;
	// seconds = secondsLeftOver - (minutes * SECONDS_IN_MINUTE);
	//
	// hoursMinSecsCenti[0] = Long.toString(hours);
	// hoursMinSecsCenti[1] = Long.toString(minutes);
	// hoursMinSecsCenti[2] = Long.toString(seconds);
	// hoursMinSecsCenti[3] = "00";
	//
	// return hoursMinSecsCenti;
	// }

}
